﻿using SS.Common.Logging;
using SS.Common.Collection;

namespace SS.WPF.Framework.Selection
{
    #region

    using System;
    using System.Collections.Generic;
    using System.Linq;

    #endregion

    /// <summary>
    /// The class implement interface ISelectionService that is used to provide the means to display selected View
    /// </summary>
    public class SelectionService : ISelectionService
    {
        /// <summary>
        /// The logger instance.
        /// </summary>
        private static readonly ILogger Logger = LoggerManager.GetLogger(typeof(SelectionService).Name);

        #region Constants and Fields

        /// <summary>
        /// The list of weak reference to handlers.
        /// </summary>
        private readonly IList<WeakReference> _weakHandlerReferences = new SyncList<WeakReference>();

        /// <summary>
        ///   The current selection.
        /// </summary>
        private ISelection _currentSelection;

        #endregion

        #region Delegates

        /// <summary>
        /// The selection changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="newSelection">
        /// The new selection.
        /// </param>
        public delegate void SelectionChanged(object sender, ISelection newSelection);

        #endregion

        #region Implementation of Interfaces

        #region ISelectionChangedProvider

        /// <summary>
        /// Get current selection.
        /// </summary>
        /// <returns>
        /// The selection.
        /// </returns>
        public ISelection GetSelection()
        {
            return this._currentSelection;
        }

        /// <summary>
        /// Set selection from specified instant of class implements interface ISelection
        /// </summary>
        /// <param name="sender">
        /// The sender that invoke this method
        /// </param>
        /// <param name="selection">
        /// Instant of class implements interface ISelection
        /// </param>
        public void SetSelection(object sender, ISelection selection)
        {
            this._currentSelection = selection;
            NotifySelectionChanged(new InternalSelectionArgs(sender, selection));
        }

        private void NotifySelectionChanged(InternalSelectionArgs args)
        {
            var theDeads = new List<WeakReference>();
            foreach (var handler in _weakHandlerReferences)
            {
                if (handler.IsAlive)
                {
                    try
                    {
                        ((ISelectionChangedHandler)(handler.Target)).SelectionChanged(args.Sender, args.Selection);
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Exception when notifing selection to [{0}]. Detail:\n{1}", handler.Target, ex);
                    }
                }
                else
                {
                    theDeads.Add(handler);
                }
            }

            foreach (var dead in theDeads)
            {
                _weakHandlerReferences.Remove(dead);
            }
        }

        #endregion

        #region ISelectionService

        /// <summary>
        /// Register selection changed handler.
        /// </summary>
        /// <param name="handler">
        /// The handler.
        /// </param>
        public void Register(ISelectionChangedHandler handler)
        {
            this._weakHandlerReferences.Add(new WeakReference(handler));
        }

        /// <summary>
        /// Unregister selection changed handler.
        /// </summary>
        /// <param name="handler">
        /// The handler.
        /// </param>
        public void Unregister(ISelectionChangedHandler handler)
        {
            var hRef = _weakHandlerReferences.FirstOrDefault(
                x => x.IsAlive && ReferenceEquals(x.Target, handler));
            if (hRef != null)
            {
                _weakHandlerReferences.Remove(hRef);
            }
        }

        #endregion

        #endregion

        /// <summary>
        /// Selection event argument.
        /// </summary>
        public class InternalSelectionArgs
        {
            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="InternalSelectionArgs"/> class.
            /// </summary>
            /// <param name="sender">
            /// The sender.
            /// </param>
            /// <param name="selection">
            /// The selection.
            /// </param>
            public InternalSelectionArgs(object sender, ISelection selection)
            {
                this.Sender = sender;
                this.Selection = selection;
            }

            #endregion

            #region Properties

            /// <summary>
            ///   Gets Selection.
            /// </summary>
            public ISelection Selection { get; private set; }

            /// <summary>
            ///   Gets Sender.
            /// </summary>
            public object Sender { get; private set; }

            #endregion
        }


        private class InternalWeakEventHandler : ISelectionChangedHandler
        {
            private readonly WeakReference _target;

            private SelectionChanged _delegate;

            public InternalWeakEventHandler(SelectionChanged @delegate)
            {
                _target = new WeakReference(@delegate.Target);
                _delegate = @delegate;
            }

            public InternalWeakEventHandler(ISelectionChangedHandler handler)
            {
                _target = new WeakReference(handler);
                _delegate = handler.SelectionChanged;
            }


            #region Implementation of IDisposable

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            /// <filterpriority>2</filterpriority>
            public void Dispose()
            {
                _delegate = null;
            }

            /// <summary>
            /// The selection changed.
            /// </summary>
            /// <param name="sender">
            /// The sender.
            /// </param>
            /// <param name="newSelection">
            /// The new selection.
            /// </param>
            public void SelectionChanged(object sender, ISelection newSelection)
            {
                if (_target.IsAlive)
                {
                    _delegate(sender, newSelection);
                }
                else
                {
                    _delegate = null;
                }
            }

            #endregion
        }
    }
}